#include "SnakerPlayer.h"
using namespace std;

SnakerCamera::SnakerCamera(int x, int y) {
    size[0] = x, size[1] = y;
    position[0] = 0, position[1] = 0;
}

string SnakerCamera::Render2String(SnakerMap* map) {
    string res = "";
    // get chunk
    int posx = position[0] + size[0] - 1, posy = position[1] + size[1] - 1;
    int cpos[2][2] = {
        {dim(position[0], chunkSize), dim(position[1], chunkSize)},
        {dim(posx, chunkSize), dim(posy, chunkSize)}};

    for (int i = cpos[0][0]; i <= cpos[1][0]; i++) {
        for (int j = cpos[0][1]; j <= cpos[1][1]; j++) {
            SnakerChunk* chk = map->FindChunk(i, j);
            map->ExpandMap(chk);  // expand
        }
    }

    int cx = dim(position[0], chunkSize), cy = dim(position[1], chunkSize);
    SnakerChunk* chk = map->FindChunk(cx, cy);
    int ncx, ncy;
    for (int i = position[0]; i <= posx; i++) {
        if (mdim(i, chunkSize) == 0) {
            cx = dim(i, chunkSize);
        }
        cy = dim(position[1], chunkSize);
        chk = map->FindChunk(cx, cy);
        for (int j = position[1]; j <= posy; j++) {
            if (mdim(j, chunkSize) == 0) {
                cy = dim(j, chunkSize);
                chk = map->FindChunk(cx, cy);
            }
            int type = map->GetTerrainType(i, j, chk);
            res += type == 0 ? '#' : (type < 0 ? ' ' : chk->fruitList[type - 1]->appearance[0]/*'P'*/);
        }
        res += '\n';
    }

    return res;
}

// char** SnakerCamera::Render2StringC(SnakerMap* map) {
//     string src = Render2String(map);
//     char** res = (char**)new char[size[0]][size[1]];
//     for (int i = 0; i < size[0]; i++) {
//         for (int j = 0; j < size[1]; j++)
//     }
// }

void SnakerCamera::Resize(int x, int y) {
    size[0] = x, size[1] = y;
}

void SnakerCamera::Move(int direct, int speed) {
    position[0] += STEP[direct][0] * speed;
    position[1] += STEP[direct][1] * speed;
}

void SnakerCamera::Move(pair<int, int> pos) {
    position[0] = pos.first - size[0] / 2;
    position[1] = pos.second - size[1] / 2;
}

bool isSafe(SnakerChunk* spawn) {
    // bfs for obstacle
    list<pair<pair<int, int>, int>> q;
    set<pair<int, int>> mark;
    q.push_back(make_pair(make_pair(chunkSize / 2, chunkSize / 2), 0));
    mark.insert(make_pair(chunkSize / 2, chunkSize / 2));
    while (!q.empty()) {
        pair<int, int> cur = q.front().first;
        int tme = q.front().second;
        if (tme > 3) {
            return true;
        }
        // if (spawn->data[cur.first][cur.second] == 0) return false;
        q.pop_front();
        for (int i = 0; i < 4; i++) {
            pair<int, int> tmp = make_pair(cur.first + STEP[i][0], cur.second + STEP[i][1]);
            if (spawn->data[tmp.first][tmp.second] != 0 && mark.find(tmp) != mark.end()) {
                q.push_back(make_pair(tmp, tme + 1));
            }
        }
    }
    return false;
}

SnakerPlayer::SnakerPlayer(SnakerChunk* spawn, SnakerMap* map_) {
    map = map_;
    score = 0;
    len = 3;
    speed = 1;
    appearance = "@#" + to_string(COLOR_YELLOW);
    map->ExpandMap(spawn);
    SnakerChunk* chk = spawn;
    // while (!isSafe(chk)) {
    //     chk = map->FindChunk(chk->cx, chk->cy + 1);
    //     map->ExpandMap(chk);
    // }

    // posList.resize(0);
    posList.clear();
    posList.push_back(make_pair((chk->cx) * chunkSize + chunkSize / 2, (chk->cy) * chunkSize + chunkSize / 2));
    posList.push_back(make_pair((chk->cx) * chunkSize + chunkSize / 2, (chk->cy) * chunkSize + chunkSize / 2 + 3));
    // int x = (chk->cx) * chunkSize + chunkSize / 2, y = (chk->cy) * chunkSize + chunkSize / 2;

    // /*
    for (list<pair<int, int>>::iterator it = posList.begin(), pit = next(posList.begin(), 1); pit != posList.end(); it++, pit++) {
        pair<int, int> prpos = *it, nxpos = *pit;
        if (prpos.first == nxpos.first) {
            int x = prpos.first;
            for (int y = prpos.second; y != nxpos.second; y += (int)((nxpos.second - prpos.second) / abs(nxpos.second - prpos.second))) {
                map->RemoveFruit(x, y);
            }
        }
    }
    // */
    isDead = false;
}

void SnakerPlayer::Die() {
    isDead = true;
    for (list<pair<int, int>>::iterator it = posList.begin(); next(it, 1) != posList.end(); it++) {
        pair<int, int> curs;
        int dir = GetDirect(next(it, 1)->first, next(it, 1)->second, it->first, it->second);
        for (curs = *it; curs != *next(it, 1); curs.first += STEP[dir][0], curs.second += STEP[dir][1]) {
            map->AddFruit(new SnakerFruit(vector<buff>{BUFF_LEN_1, BUFF_SCORE_100}, "P#2"), curs.first, curs.second);
        }
    }
}

SnakerPlayer::~SnakerPlayer() {
    Die();
}

void SnakerPlayer::AddBuff(vector<buff> incomingBuff) {
    // buffList.push_back(incomingBuff);
    for (vector<buff>::iterator it = incomingBuff.begin(); it != incomingBuff.end(); it++) buffList.push_back(*it);
}

int SnakerPlayer::Update(int cmd, SnakerCamera* cam) {
    int step = min(max(speed, 0), 3);
    pair<int, int> curs;
    if (len > 1) {
        // move, applybuff, interact
        pair<int, int> head, headn, tail, tailp;
        head = posList.front();
        headn = *next(posList.begin(), 1);
        tail = posList.back();
        tailp = *prev(posList.end(), 2);

        curs = head;
        int dir = GetDirect(head.first, head.second, headn.first, headn.second);
        bool eqDirection = (dir == STEP_LEFT || dir == STEP_RIGHT) && (cmd == STEP_LEFT || cmd == STEP_RIGHT) || (dir == STEP_UP || dir == STEP_DOWN) && (cmd == STEP_UP || cmd == STEP_DOWN);
        if (eqDirection && dir != cmd) cmd = dir;

        // /*
        // 爬~~~
        // head
        for (int i = 0; i < speed; i++) {
            pair tcurs = make_pair(STEP[cmd][0] + curs.first, STEP[cmd][1] + curs.second);
            int type = map->GetTerrainType(tcurs.first, tcurs.second);
            if (!type) {
                Die();
                return 1;
            }
            if (type > 0) {
                AddBuff(map->GetFruit(map->GetChunk(tcurs.first, tcurs.second), type)->buffList);
                map->RemoveFruit(tcurs.first, tcurs.second);
            }
            curs = tcurs;
        }

        posList.push_front(curs);
        int cnt = 0;
        int numDel = 0;
        for (list<pair<int, int>>::iterator it = prev(posList.end(), 1); it != posList.begin(); it--) {
            dir = GetDirect(prev(it, 1)->first, prev(it, 1)->second, it->first, it->second);
            numDel++;
            for (curs = *it; cnt < speed && curs != *prev(it, 1); cnt++, curs = make_pair(curs.first + STEP[dir][0], curs.second + STEP[dir][1]))
                ;
            if (cnt == speed) {
                // if (curs != *prev(it, 1)) numDel--;
                break;
            }
        }
        while (numDel--) posList.pop_back();
        posList.push_back(curs);
        // */

        // /*
        // apply buff
        for (vector<buff>::iterator it = buffList.begin(); it != buffList.end(); it++) {
            switch (it->lastime) {
                case 0:
                    break;

                default:
                    it->lastime--;
                    switch (it->id) {
                        case 0:  // score 100
                            score += 100;
                            break;

                        case 1:  // len 1
                            len++;
                            tail = posList.back();
                            tailp = *prev(posList.end(), 1);
                            dir = GetDirect(tail.first, tail.second, tailp.first, tailp.second);
                            curs = make_pair(tail.first + STEP[dir][0], tail.second + STEP[dir][1]);
                            posList.pop_back();
                            posList.push_back(curs);
                            break;

                        default:
                            break;
                    }
                    break;
            }
        }
        // */
    }

    // /*

    chunkSet.clear();
    paintBoard.clear();
    pair<int, int> head = posList.front();
    for (list<pair<int, int>>::iterator it = posList.begin(); next(it, 1) != posList.end(); it++) {
        int dir = GetDirect(next(it, 1)->first, next(it, 1)->second, it->first, it->second);
        chunkSet.insert(map->GetChunk(it->first, it->second));
        for (curs = *it; curs != *(next(it, 1)); curs.first += STEP[dir][0], curs.second += STEP[dir][1] /*curs = make_pair(curs.first + STEP[dir][0], curs.second + STEP[dir][1])*/) {
            if (!mdim(curs.first, chunkSize) || !mdim(curs.second, chunkSize)) chunkSet.insert(map->GetChunk(curs.first, curs.second));

            /*
             * Here is a function due to be improved.
             * Get ready for paint. 
             * needed to reverse, when painting. 
             */
            // pair<int, int> coordi(head.first - (cam->position[0] + cam->size[0] / 2), head.second - (cam->position[1] + cam->size[1] / 2));
            cam->Move(head);
            pair<int, int> cord(curs.first - (cam->position[0]), curs.second - (cam->position[1]));
            if (cord.first < 0 || cord.first > cam->size[0] - 1 || cord.second < 0 || cord.second > cam->size[1] - 1) continue;
            if (curs == head)
                paintBoard.push_back(make_pair(make_pair('@', COLOR_GREEN), cord));
            else
                paintBoard.push_back(make_pair(make_pair('O', COLOR_YELLOW), cord));
        }
    }

    // */

        return 0;
}